package com.googlecode.chios.jnet.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.chios.jnet.exception.NetException;
import com.googlecode.chios.jnet.worker.AioWorker;
import com.googlecode.chios.jnet.worker.WorkerContext;

public class ServerSocketBoss {

	private Logger logger = LoggerFactory.getLogger(ServerSocketBoss.class);
	
	private AsynchronousServerSocketChannel serverChannel;

	private AsynchronousChannelGroup group;// boss线程组, 交由jdk负责

	private CompletionHandler<AsynchronousSocketChannel, ServerSocketBoss> acceptCompletionHandler;

	private boolean isStart;
	
	private boolean isAwait;
	
	private WorkerContext workerContext;
	
	private Configration configration;
	
	public ServerSocketBoss(Configration configration) {
		try {
			this.configration = configration;
			this.group = AsynchronousChannelGroup.withThreadPool(Executors.newCachedThreadPool());
			this.acceptCompletionHandler =  new AcceptCompletionHandler();
			this.workerContext = WorkerContext.open();
		} catch (IOException e) {
			throw new NetException(e);
		}
	}

	public void bind(int port) {
		try {
			SocketAddress address = new InetSocketAddress(port);
			this.serverChannel = AsynchronousServerSocketChannel.open(group);
			this.configration.config(this.serverChannel);
			this.serverChannel.bind(address);
			this.isStart = true;

			this.accept();
			logger.debug("server boss listening port: " + port);
		} catch (IOException e) {
			throw new NetException("open channel failed", e);
		}
	}

	public void bindAndAwait(int port) {
		this.isAwait = true;
		this.bind(port);
	}
	
	private void accept() {
		if (this.isStart && serverChannel.isOpen()) {
			if(isAwait) {
				AsynchronousSocketChannel acceptedChannel = null;
				acceptedChannel = awaitAccept();
				acceptCompletionHandler.completed(acceptedChannel, this);
			} else {
				this.serverChannel.accept(this, acceptCompletionHandler);
			}
		} else {
			throw new NetException("the server boss has been closed");
		}
	}

	private AsynchronousSocketChannel awaitAccept() {
		Future<AsynchronousSocketChannel> accept = this.serverChannel.accept();
		AsynchronousSocketChannel acceptedChannel = null;
		while(isStart && isAwait) {
			try {
				acceptedChannel = accept.get(10, TimeUnit.SECONDS);// 10 seconds
			} catch (InterruptedException e) {
				logger.debug("accept interrupted by " + e);
			} catch (ExecutionException e) {
				logger.debug("execution failed by " + e);
			} catch (TimeoutException e) {
				logger.debug("time out by " + e);
			}
			
			if(acceptedChannel != null) {
				break;
			}
		}
		return acceptedChannel;
	}
	
	public void pendingAccept() {
		this.accept();
	}
	
	class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, ServerSocketBoss> {

		private Logger logger = LoggerFactory.getLogger(AcceptCompletionHandler.class);
		
		@Override
		public void completed(AsynchronousSocketChannel channel, ServerSocketBoss attachment) {
			try {
				logger.debug("client [" + channel.getRemoteAddress() + "] conneted!!!");
				System.out.println("client [" + channel.getRemoteAddress() + "] conneted!!!");
				configration.config(channel);
				AioSession session = new AioSession(channel, configration);
				register(session);
				session.start();
			} catch (Exception e) {
				logger.debug(e.getMessage(), e.getCause());
			} finally {
				attachment.pendingAccept();
			}
		}

		private void register(AioSession session) {
			AioWorker worker = workerContext.nextWorker();
			worker.registerSession(session);
			session.workWith(worker);
		}

		@Override
		public void failed(Throwable exc, ServerSocketBoss attachment) {
			logger.debug(exc.getMessage(), exc.getCause());
			attachment.pendingAccept();
		}
	}

}
